package com.lzwork.gpt.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSONObject;
import com.lzwork.gpt.db.entity.ApiEditEntity;
import com.lzwork.gpt.db.entity.ApiEntity;
import com.lzwork.gpt.db.entity.ApiLogEntity;
import com.lzwork.gpt.db.entity.AppEntity;
import com.lzwork.gpt.db.entity.ModelEntity;
import com.lzwork.gpt.service.ApiDataService;
import com.lzwork.gpt.utility.caller.GptCallResult;
import com.lzwork.gpt.utility.caller.GptCaller;
import com.lzwork.gpt.utility.caller.GptCaller.CallType;
import com.lzwork.gpt.utility.caller.OpenApiCaller;
import com.lzwork.gpt.utility.common.ParserCaller;
import com.lzwork.utility.collection.MapReader;
import com.lzwork.utility.logger.LOGGER;
import com.lzwork.utility.pair.StringPair;
import com.lzwork.utility.pair.StringTuple;
import com.lzwork.utility.str.StrUtility;
import com.lzwork.web.BaseController;
import com.lzwork.web.JsonKey;
import com.lzwork.web.data.BoolResultInfo;
import com.lzwork.web.data.CallResultInfo;

/**
 * @author LiZheng
 * @date 2023年8月29日 下午5:03:15
 */

@Controller
@RequestMapping(value = "/model/*")
public class ApiModelController extends BaseController
{
	@Autowired
	ApiDataService service;

	@GetMapping(value = "/callTypes")
	@ResponseBody
	public JSONObject callTypes()
	{
		JSONObject result = new JSONObject();
		result.put("calltype", CallType.allInfo());

		return result;
	}

	@GetMapping(value = "/info")
	@ResponseBody
	public JSONObject info(@RequestParam(value = "fullInfo", defaultValue = "false") Boolean fullInfo,
			@RequestParam(value = "mSort", defaultValue = "0") int mSort,
			@RequestParam(value = "aSort", defaultValue = "0") int aSort,
			@RequestParam(value = "sSort", defaultValue = "0") int sSort)
	{
		JSONObject result = new JSONObject();

		List<ModelEntity> models = service.getAllModel(mSort);
		List<ApiEntity> apis = service.getAllApi(fullInfo, aSort);
		List<AppEntity> apps = service.getAllApp(fullInfo, sSort);

		result.put("models", models);

		List<JSONObject> objs = new ArrayList<>();
		for (ApiEntity e : apis)
		{
			objs.add(e.toJObj(fullInfo, null));
		}
		result.put("apis", objs);

		List<JSONObject> appObjs = new ArrayList<>();
		for (AppEntity e : apps)
		{
			appObjs.add(e.toJObj(fullInfo));
		}
		result.put("apps", appObjs);

		result.put("calltype", CallType.allInfo());
		result.put("access", service.getAccessPath());

		return result;
	}

	@GetMapping(value = "/models")
	@ResponseBody
	public JSONObject models(@RequestParam(value = "mSort", defaultValue = "0") int mSort)
	{
		JSONObject result = new JSONObject();

		List<ModelEntity> model = service.getAllModel(mSort);
		result.put("models", model);

		return result;
	}

	@GetMapping(value = "/getmodel")
	@ResponseBody
	public JSONObject getmodel(@RequestParam int id)
	{
		JSONObject result = new JSONObject();
		ModelEntity model = service.getModel(id);

		if (model == null)
			result.put(JsonKey.KEY_ERRMSG, "Model " + id + " not found.");
		else
			result.put("data", model);

		return result;
	}

	@GetMapping(value = "/apis")
	@ResponseBody
	public JSONObject apis(@RequestParam(value = "fullInfo", defaultValue = "false") Boolean fullInfo,
			@RequestParam(value = "editMode", defaultValue = "false") Boolean editMode,
			@RequestParam(value = "aSort", defaultValue = "0") int aSort)
	{
		JSONObject result = new JSONObject();

		List<ApiEntity> api = service.getAllApi(fullInfo, aSort);

		List<JSONObject> objs = new ArrayList<>();
		for (ApiEntity e : api)
		{
			objs.add(e.toJObj(fullInfo, editMode ? service.getApiEdit(e.getId()) : null));
		}

		result.put("apis", objs);

		return result;
	}

	@GetMapping(value = "/getapi")
	@ResponseBody
	public JSONObject getapi(@RequestParam int id, @RequestParam(value = "fullInfo", defaultValue = "true") Boolean fullInfo)
	{
		JSONObject result = new JSONObject();
		ApiEntity api = service.getApi(id);

		int stay = 1;

		if (api == null)
		{
			result.put(JsonKey.KEY_ERRMSG, "Api " + id + " not found.");

			if (fullInfo)
				stay = -1;
		} else
		{
			ApiEditEntity edit = service.getApiEdit(id);
			result.put("data", api.toJObj(fullInfo, edit));
		}

		result.put("stay", stay);

		return result;
	}

	@GetMapping(value = "/pickapi")
	@ResponseBody
	public JSONObject pickapi(@RequestParam String apikey,
			@RequestParam(value = "fullInfo", defaultValue = "true") Boolean fullInfo)
	{
		JSONObject result = new JSONObject();
		ApiEntity api = service.getApi(apikey);

		if (api == null)
			result.put(JsonKey.KEY_ERRMSG, "Api " + apikey + " not found.");
		else
		{
			result.put("data", api.toJObj(fullInfo, null));
		}

		return result;
	}

	@GetMapping(value = "/apps")
	@ResponseBody
	public JSONObject apps(@RequestParam(value = "fullInfo", defaultValue = "false") Boolean fullInfo,
			@RequestParam(value = "editMode", defaultValue = "false") Boolean editMode,
			@RequestParam(value = "sSort", defaultValue = "0") int sSort)
	{
		JSONObject result = new JSONObject();

		List<AppEntity> app = service.getAllApp(fullInfo, sSort);

		List<JSONObject> objs = new ArrayList<>();
		for (AppEntity e : app)
		{
			objs.add(e.toJObj(fullInfo));
		}

		result.put("apps", objs);

		return result;
	}

	@GetMapping(value = "/getapp")
	@ResponseBody
	public JSONObject getapp(@RequestParam int id, @RequestParam(value = "fullInfo", defaultValue = "true") Boolean fullInfo)
	{
		JSONObject result = new JSONObject();
		AppEntity app = service.getApp(id);

		int stay = 1;

		if (app == null)
		{
			result.put(JsonKey.KEY_ERRMSG, "App " + id + " not found.");

			if (fullInfo)
				stay = -1;
		} else
		{
			result.put("data", app.toJObj(fullInfo));
		}

		result.put("stay", stay);

		return result;
	}

	@GetMapping(value = "/pickapp")
	@ResponseBody
	public JSONObject pickapp(@RequestParam String appkey,
			@RequestParam(value = "fullInfo", defaultValue = "true") Boolean fullInfo)
	{
		JSONObject result = new JSONObject();
		AppEntity app = service.getApp(appkey);

		if (app == null)
			result.put(JsonKey.KEY_ERRMSG, "App " + appkey + " not found.");
		else
		{
			result.put("data", app.toJObj(fullInfo));
		}

		return result;
	}

	@PostMapping(value = "/createmodel", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject createModel(@RequestBody Map<?, ?> map)
	{
		JSONObject result = new JSONObject();

		String title = getMapStr(map, "title");
		ModelEntity entity = new ModelEntity();
		entity.setTitle(title);

		CallResultInfo<ModelEntity> info = service.makeModelEntity(entity);

		if (info.target() == null)
			result.put(JsonKey.KEY_ERRMSG, info.info());
		else
			result.put("id", info.target().getId());

		return result;
	}

	@PostMapping(value = "/copymodel", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject copymodel(@RequestBody Map<?, ?> map)
	{
		JSONObject result = new JSONObject();

		String title = getMapStr(map, "title");
		int source = getMapInt(map, "source", -1);

		ModelEntity entity = service.getModel(source);
		if (entity == null)
			return errReturn("Origin model [" + source + "] not exist.");

		entity.setTitle(title);
		CallResultInfo<ModelEntity> info = service.makeModelEntity(entity);

		if (info.target() == null)
			result.put(JsonKey.KEY_ERRMSG, info.info());
		else
			result.put("id", info.target().getId());

		return result;
	}

	@PostMapping(value = "/updatemodel", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject updatemodel(@RequestBody Map<?, ?> map)
	{
		JSONObject result = new JSONObject();

		ModelEntity entity = ModelEntity.loadFromMap(map);
		BoolResultInfo info = service.setModelEntity(entity);

		if (info.isDone())
		{
			return result;
		}

		return info.errJobj();
	}

	@GetMapping(value = "/delmodel")
	@ResponseBody
	public JSONObject delmodel(@RequestParam int id)
	{
		BoolResultInfo result = service.deleteModel(id);

		return result.toJobj();
	}

	@PostMapping(value = "/chatTest", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject chatTest(@RequestBody Map<?, ?> map)
	{
		JSONObject result = new JSONObject();
		String err = null;

		String content = getMapStr(map, "content");
		String url = getMapStr(map, "url");
		int type = getMapInt(map, "type", -1);
		String model = getMapStr(map, "model");
		String modelToken = getMapStr(map, "token");
		boolean dolog = getMapBool(map, "dolog");

		if (!StrUtility.stringValid(url))
			return errReturn("Url is empty.");

		if (!StrUtility.stringValid(content))
			return errReturn("Content is empty.");

		GptCaller caller;

		CallType ct = CallType.parseType(type);
		if (ct == CallType.Unknown)
			return errReturn("Unknown call type: " + type);
		else if (ct == CallType.OpenApi)
			caller = new OpenApiCaller(url, modelToken, model);
		else
			caller = new ParserCaller(url, dolog);

		try
		{
			GptCallResult cr = caller.callChat(content, null, null);
			Exception e = cr.getException();
			if (e != null)
				throw e;

			result.put("content", cr.getResponse());
			result.put("cost", cr.getGpuCost());

		} catch (Exception e)
		{
			err = LOGGER.exceptionInfo(e, false);
			return errReturn(err);
		}

		return result;
	}

	@PostMapping(value = "/createapi", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject createApi(@RequestBody Map<?, ?> map)
	{
		JSONObject result = new JSONObject();

		String title = getMapStr(map, "title");
		ApiEntity entity = new ApiEntity();
		entity.setTitle(title);

		CallResultInfo<ApiEntity> info = service.makeApiEntity(entity, null);

		if (info.target() == null)
			result.put(JsonKey.KEY_ERRMSG, info.info());
		else
		{
			result.put("id", info.target().getId());
		}

		return result;
	}

	@PostMapping(value = "/copyapi", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject copyapi(@RequestBody Map<?, ?> map)
	{
		JSONObject result = new JSONObject();

		String title = getMapStr(map, "title");
		int source = getMapInt(map, "source", -1);

		ApiEntity entity = service.getApi(source);
		if (entity == null)
			return errReturn("Origin api [" + source + "] not exist.");

		entity.setTitle(title);
		ApiEditEntity prev = service.getApiEdit(source);

		CallResultInfo<ApiEntity> info = service.makeApiEntity(entity, prev);

		if (info.target() == null)
			result.put(JsonKey.KEY_ERRMSG, info.info());
		else
		{
			int id = info.target().getId();
			result.put("id", id);
		}

		return result;
	}

	public static class ApiEditParam
	{
		List<StringPair> paramValue = new ArrayList<>();
		List<StringPair> paramDefault = new ArrayList<>();
		List<StringTuple> fullParam = new ArrayList<>();

		public ApiEditParam(List<?> input)
		{
			for (Object o : input)
			{
				if (o instanceof Map)
				{
					Map<?, ?> map = (Map<?, ?>) o;
					String key = MapReader.getMapStr(map, "k");
					String value = MapReader.getMapStr(map, "v");
					String def = MapReader.getMapStr(map, "d");
					String title = MapReader.getMapStr(map, "t");

					if (StrUtility.stringValid(key))
					{
						paramDefault.add(new StringPair(key, def));
						paramValue.add(new StringPair(key, value));
						fullParam.add(new StringTuple(key, title, def));
					}
				}
			}
		}

		public List<StringPair> makeParam()
		{
			List<StringPair> result = new ArrayList<>();

			for (StringTuple st : fullParam)
			{
				String keyStr = st.first();
				if (keyStr.isEmpty())
					continue;

				String vStr = st.second();
				if (!StrUtility.stringValid(vStr))
					vStr = st.third();

				result.add(new StringPair(keyStr, vStr));
			}

			return result;
		}
	}

	@PostMapping(value = "/updateapi", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject updateapi(@RequestBody Map<?, ?> map)
	{
		JSONObject result = new JSONObject();

		ApiEntity entity = ApiEntity.loadFromMap(map);

		List<?> params = getMapArray(map, "paramdata");

		ApiEditParam paramData = new ApiEditParam(params);

		BoolResultInfo info = entity.resetParamContent(paramData.fullParam);
		if (!info.isDone())
			return info.errJobj();

		info = service.setApiEntity(entity);

		if (!info.isDone())
			return info.errJobj();

		ApiEditEntity edit = new ApiEditEntity();
		edit.setId(entity.getId());
		edit.setParams(paramData.paramValue);
		edit.setPromptUh(getMapInt(map, "promptUh"));
		edit.setPromptSh(getMapInt(map, "promptSh"));
		service.setApiEditEntity(edit);

		return result;
	}

	@PostMapping(value = "/setapikey", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject setapikey(@RequestBody Map<?, ?> map)
	{
		int id = getMapInt(map, "id");
		String key = getMapStr(map, "key");

		BoolResultInfo info = service.resetApiKey(id, key);

		JSONObject result = info.toJobj();

		if (info.isDone())
		{
			result.put("apikey", key);
			result.put("url", ApiJobController.apiKeyToUrl(service.getAccessPath(), key));
		}

		return result;
	}

	@GetMapping(value = "/delapi")
	@ResponseBody
	public JSONObject delapi(@RequestParam int id)
	{
		BoolResultInfo result = service.deleteApi(id);

		return result.toJobj();
	}

	@PostMapping(value = "/apiTest", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject apiTest(@RequestBody Map<?, ?> map)
	{
		ApiEntity localEntity = ApiEntity.loadFromMap(map);

		if (localEntity.getModelid() < 0)
			return errReturn("API模型未指定");

		ModelEntity model = service.getModel(localEntity.getModelid());
		if (model == null)
			return errReturn("使用模型不存在：id=" + localEntity.getModelid());

		List<?> params = getMapArray(map, "paramdata");
		ApiEditParam param = new ApiEditParam(params);
		boolean dolog = getMapBool(map, "dolog");

		localEntity.resetParamContent(param.fullParam);

		return service.apiCall(localEntity, model, param.paramValue, true, dolog);
	}

	@PostMapping(value = "/saveTestLog", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject saveTestLog(@RequestBody Map<?, ?> map)
	{
		JSONObject result = new JSONObject();

		ApiLogEntity entity = ApiLogEntity.loadFromMap(map);

		BoolResultInfo info = service.addLog(entity);

		if (!info.isDone())
			return info.errJobj();

		result.put("info", info.info());
		return result;
	}

	@GetMapping(value = "/getapilog")
	@ResponseBody
	public JSONObject getapilog(@RequestParam int api)
	{
		JSONObject result = new JSONObject();

		List<ApiLogEntity> logs = service.getLogByApi(api);
		result.put("data", logs);

		return result;
	}

	@GetMapping(value = "/getapilognum")
	@ResponseBody
	public JSONObject getapilognum(@RequestParam int api)
	{
		JSONObject result = new JSONObject();

		int num = service.getLogNumByApi(api);
		result.put("num", num);

		return result;
	}

	@GetMapping(value = "/dellog")
	@ResponseBody
	public JSONObject dellog(@RequestParam int api, @RequestParam int id)
	{
		BoolResultInfo result = service.delLog(api, id);

		return result.toJobj();
	}

	@PostMapping(value = "/promptTest", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public JSONObject promptTest(@RequestBody Map<?, ?> map)
	{
		ApiEntity localEntity = ApiEntity.loadFromMap(map);
		List<?> params = getMapArray(map, "paramdata");
		ApiEditParam param = new ApiEditParam(params);
		localEntity.resetParamContent(param.fullParam);
		String prompt = localEntity.makePrompt(param.paramValue, true);

		JSONObject result = new JSONObject();
		result.put("prompt", prompt);

		return result;
	}
}
